﻿#include "Drawer.h"

#include <glm/gtc/type_ptr.hpp>

cv::Mat processLayer1(const cv::Mat& layer1) {
    std::vector<cv::Mat> channels;
    cv::split(layer1, channels);
    cv::Mat alpha = channels[2].clone();
    cv::Mat newLayer1;
    cv::merge(std::vector<cv::Mat>{cv::Mat::ones(layer1.size(), CV_8UC1) * 255,
        cv::Mat::ones(layer1.size(), CV_8UC1) * 255,
        cv::Mat::ones(layer1.size(), CV_8UC1) * 255,
        alpha}, newLayer1);
    return newLayer1;
}

cv::Mat processMergedLayers(const cv::Mat& record_pic, const cv::Mat& layer2, const cv::Mat& layer3) {
    cv::flip(record_pic, record_pic, 0);
    std::vector<cv::Mat> pic_channels, layer2_channels, layer3_channels;
    cv::split(record_pic, pic_channels);
    cv::split(layer2, layer2_channels);
    cv::split(layer3, layer3_channels);
    pic_channels[3] = layer2_channels[3];
    cv::Mat merged(record_pic.rows, record_pic.cols, CV_8UC4, cv::Scalar(0, 0, 0, 0));
    cv::merge(pic_channels, merged);
    cv::Rect roi((merged.cols - layer3.cols) / 2, (merged.rows - layer3.rows) / 2, layer3.cols, layer3.rows);
    cv::Mat layer3_alpha;
    layer3_channels[3].convertTo(layer3_alpha, CV_32FC1);
    cv::blendLinear(merged(roi), layer3, 255 - layer3_alpha, layer3_alpha, merged(roi));
    return merged;
}

GLuint createTexture(const cv::Mat& img) {
    GLuint tex;
    glGenTextures(1, &tex);
    glBindTexture(GL_TEXTURE_2D, tex);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, img.cols, img.rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, img.data);
    return tex;
}

GLuint createVAO(const std::vector<RecordVertex>& vertices) {
    GLuint vao, vbo;
    glGenVertexArrays(1, &vao);
    glGenBuffers(1, &vbo);
    glBindVertexArray(vao);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(RecordVertex), vertices.data(), GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(RecordVertex), (void*)0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(RecordVertex), (void*)offsetof(RecordVertex, texCoord));
    glBindVertexArray(0);
    return vao;
}

Record Drawer::initRecord(cv::Mat& layer0, cv::Mat& layer1, cv::Mat& layer2, cv::Mat& layer3,
    cv::Mat& record_pic, GLuint shader_program, float dps,
    float fw, float fh, float fr) {
    Record rec;
    rec.frame_width = fw;
    rec.frame_height = fh;
    rec.shader_program = shader_program;
    rec.delta_angle = dps / fr;
    rec.current_angle = 0.0f;

    // Process layers
    cv::Mat processed_layer1 = processLayer1(layer1);
    cv::Mat merged_layers = processMergedLayers(record_pic, layer2, layer3);

    // Create textures
    rec.layer0_tex = createTexture(layer0);
    rec.layer1_tex = createTexture(processed_layer1);
    rec.merged_tex = createTexture(merged_layers);

    // Create VAOs
    float cx = fw / 2, cy = fh / 2;
    auto createVertices = [cx, cy](const cv::Mat& mat) {
        float hw = mat.cols / 2.0f, hh = mat.rows / 2.0f;
        return std::vector<RecordVertex>{
            {{cx - hw, cy - hh}, { 0,0 }},
            { {cx + hw, cy - hh}, {1,0} },
            { {cx + hw, cy + hh}, {1,1} },
            { {cx - hw, cy + hh}, {0,1} }
        };
        };

    rec.layer0_vao = createVAO(createVertices(layer0));
    rec.layer1_vao = createVAO(createVertices(layer0)); // Same size as layer0
    rec.merged_vao = createVAO(createVertices(merged_layers));
    glGenFramebuffers(1, &rec.fbo);
    glBindFramebuffer(GL_FRAMEBUFFER, rec.fbo);

    // 创建颜色附件纹理
    glGenTextures(1, &rec.fbo_texture);
    glBindTexture(GL_TEXTURE_2D, rec.fbo_texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fw, fh, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, rec.fbo_texture, 0);

    // 创建渲染缓冲对象（用于深度和模板附件）
    glGenRenderbuffers(1, &rec.rbo);
    glBindRenderbuffer(GL_RENDERBUFFER, rec.rbo);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, fw, fh);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rec.rbo);

    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
        throw "FBO不完整。";
    }
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    return rec;
}

void Drawer::drawRecord(Record& rec) {
    glBindFramebuffer(GL_FRAMEBUFFER, rec.fbo);
    glViewport(0, 0, rec.frame_width, rec.frame_height);
    Utils::clearFrame(rec.fbo);
    glUseProgram(rec.shader_program);
    glm::mat4 proj = glm::ortho(0.0f, rec.frame_width, rec.frame_height, 0.0f, -1.0f, 1.0f);
    GLuint projLoc = glGetUniformLocation(rec.shader_program, "projection");
    glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(proj));

    auto drawLayer = [&](GLuint vao, GLuint tex, float angle) {
        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        glBindVertexArray(vao);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, tex);
        glm::mat4 model(1.0f);
        model = glm::translate(model, glm::vec3(rec.frame_width / 2, rec.frame_height / 2, 0));
        model = glm::rotate(model, glm::radians(angle), glm::vec3(0, 0, -1));
        model = glm::translate(model, glm::vec3(-rec.frame_width / 2, -rec.frame_height / 2, 0));
        GLuint modelLoc = glGetUniformLocation(rec.shader_program, "model");
        glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
        glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
        glDisable(GL_BLEND);
        };

    rec.current_angle += rec.delta_angle;
    // Draw layer0
    drawLayer(rec.layer0_vao, rec.layer0_tex, rec.current_angle);
    // Draw layer1 (no rotation)
    drawLayer(rec.layer1_vao, rec.layer1_tex, 0.0f);
    // Draw merged layers
    drawLayer(rec.merged_vao, rec.merged_tex, rec.current_angle);

    glBindVertexArray(0);
    glUseProgram(0);
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
}